package chapter2_nio.conn;

import java.net.InetSocketAddress;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;

public class Client {
    public static void main(String[] args) throws Exception {
        Client client=new Client("127.0.0.1",9999);
        client.communicate();
    }

    private Selector selector;
    /** 创建selector选择器对象，同时创建一个SocketChannel对象(用于连接服务端)。 然后将SocketChannel channel注册到选择器上，
     *  最后调用SocketChannel channel的connect()方法连接服务端。
     */
    protected  Client(String serverIP, int port)throws  Exception{//√ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √
        //用Selector.open()可以获得一个新的选择器实例
        selector=Selector.open();
        System.out.println(selector);
        SocketChannel channel=SocketChannel.open();//获取ServerSocketChannel实例
        channel.configureBlocking(false);
        //该函数用于向Selector注册SocketChannel，并指定此SocketChannel作用是和服务端连接
        channel.register(selector, SelectionKey.OP_CONNECT);
        //通过此SocketChannel连接服务端
        channel.connect(new InetSocketAddress(serverIP,port));
    }


    public void closeChannel(SelectionKey selectionKey)throws Exception{//√ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √
        selectionKey.channel().close();
        //关闭这个selectionKey对应的选择器Selector(会断开与该选择器相关的所有通道连接)
        selectionKey.selector().close();
    }

    /**获取selector中的所有selectionKey。 并判断此selectionKey是否是可连接的(是可连接的就连接，否则就进行数据的读写操作)。
     *    如果是可连接的就检查是否已经连接到服务端如果连接上了，就将该channel重新注册到选择器上(之前此channel用于连接服务端，以后此channel将用于读写数据)。
     *    如果不是可连接的就调用session方法进行数据的读写操作
     * 同时从获取的所有selectionKey中移除已经处理过的selectionKey(剩余的selectionKey是未处理的)
     *    */
    public void communicate()throws Exception{//√ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √
        while(selector.isOpen()){
            //用选择器进行阻塞式等待，直到有某个SocketChannel通道准备好读操作
            // 或写操作或者准备和Server进行连接操作(或设置的其他操作触发)才会继续往下运行
            selector.select();
            //获取selector中的所有selectionKey组成的集合，并处理集合中每个selectionKey对应的SocketChannel的事件，
            // 处理完一个就将其对应的selectionKey从Set集合中移除
            Set<SelectionKey> selectionKeySet=selector.selectedKeys();
            Iterator<SelectionKey> iterator=selectionKeySet.iterator();
            while(iterator.hasNext()){
                SelectionKey selectionKey=iterator.next();
                if(selectionKey.isConnectable()){
                    SocketChannel channel=(SocketChannel) selectionKey.channel();
                    //该代码片段是用于等待SocketChannel完成连接的。(如果这个SocketChannel没连接完成，就继续循环检查，直到完成连接再继续向下执行)
                    //(SocketChannel是一个非阻塞模式的网络I/O模型，因此在进行连接时，可能不会立即完成)
                    while(!channel.finishConnect()){//
                    }
                    System.out.println("客户端连接成功，可以和服务端进行数据传输...");
                    //连接成功后需要将此SocketChannel channel重新注册到selector选择器上(因为此通道下一次就要用于数据传输而不是连接的作用)
                    registerOnConnected(channel,selector);
                }else{//如果此selectionKey不是和服务端进行连接，则和服务端进行数据传输
                    session(selectionKey);
                }//从获取的所有selectionKey中移除已经处理过的selectionKey
                iterator.remove();//(剩余的selectionKey是未处理的)
            }
            System.out.println("客户端的操作完毕");
        }
    }


    public void session(SelectionKey selectionKey)throws Exception{
        System.out.println("模拟和客户端互相传输数据");
    }

    public void registerOnConnected(SocketChannel channel,Selector selector)throws Exception{
        System.out.println("模拟将SocketChannel:"+channel+"注册到Selector选择器上");
    }


}